/*
 * Copyright (c) 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.api.data.calendar.v2;

import com.google.api.client.googleapis.GoogleUrl;
import com.google.api.client.http.HttpExecuteIntercepter;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponseException;
import com.google.api.data.AbstractAtomService;
import com.google.api.data.LoggingUtils;
import com.google.api.data.calendar.v2.model.AccessControlList;
import com.google.api.data.calendar.v2.model.Calendar;
import com.google.api.data.calendar.v2.model.CalendarList;
import com.google.api.data.calendar.v2.model.Event;
import com.google.api.data.calendar.v2.model.EventList;
import com.google.api.data.calendar.v2.model.FreeBusy;
import com.google.api.data.calendar.v2.model.SettingsEntry;
import com.google.api.data.calendar.v2.model.SettingsList;
import com.google.api.data.gdata.v2.model.Reminder;
import com.google.api.data.gdata.v2.model.batch.BatchFeed;

import java.io.IOException;
import java.util.List;

/**
 * Constants for the Google Contacts Data API.
 * 
 * @author Alain Vongsouvanh
 */
public class CalendarService extends AbstractAtomService {

  public int maxRedirectAttempt = 5;
  private String gsessionid = "";

  private class RedirectHandler implements HttpExecuteIntercepter {

    private CalendarService service;

    public RedirectHandler(CalendarService s) {
      if (s == null)
        throw new NullPointerException();
      service = s;
    }

    public void intercept(HttpRequest request) throws IOException {
      if (service.gsessionid != null) {
        request.url.set("gsessionid", service.gsessionid);
      }
    }

  }

  // ============= Constructors ================

  /**
   * Constructs a Google Contacts API service.
   * 
   * @param applicationName The application Name
   */
  public CalendarService(String applicationName) {
    super(applicationName, CalendarApiInfo.NAMESPACE_DICTIONARY);
    getTransport().intercepters.add(0, new RedirectHandler(this));
  }

  // =============== Setting up Service Information ================

  @Override
  protected String getDefaultApiVersion() {
    return CalendarApiInfo.VERSION;
  }

  @Override
  protected String getClientLoginServiceCode() {
    return CalendarApiInfo.AUTH_TOKEN_TYPE;
  }

  @Override
  protected String getServiceAuthenticationScope() {
    return CalendarApiInfo.AUTH_SCOPE;
  }

  // ============= API Requests Methods ================

  /**
   * Returns the Settings for the user.
   * 
   * @param email The user for which to retrieve the settings
   * @return The settings for the user
   * @throws IOException
   */
  public SettingsList getSettings(String email) throws IOException {
    return executeGet(UrlFactory.getSettingsFeedUrl(email), null, false, SettingsList.class);
  }

  /**
   * Returns the Settings for the authenticated user.
   * 
   * @return The settings for the authenticated user
   * @throws IOException
   */
  public SettingsList getSettings() throws IOException {
    return executeGet(UrlFactory.getSettingsFeedUrl(), null, false, SettingsList.class);
  }

  /**
   * Returns the list of Calendars at the given Feed URL.
   * 
   * @param url The URL of the Calendar Feed
   * @return The List of calendars available at the given Feed URL
   * @throws IOException
   */
  public CalendarList getCalendars(CalendarUrl url) throws IOException {
    return executeGet(url, null, false, CalendarList.class);
  }

  /**
   * Returns the list of All Subscribed Calendars of the authenticated account.
   * 
   * @return The List of subscribed calendars of the authenticated account
   * @throws IOException
   */
  public CalendarList getAllCalendars() throws IOException {
    return executeGet(UrlFactory.getAllCalendarsFeedUrl(), null, false, CalendarList.class);
  }

  /**
   * Returns the list of Calendars owned by the authenticated account.
   * 
   * @return The List of calendars owned by the authenticated account
   * @throws IOException
   */
  public CalendarList getOwnCalendars() throws IOException {
    return executeGet(UrlFactory.getOwnCalendarsFeedUrl(), null, false, CalendarList.class);
  }

  /**
   * Returns the list of Events for the calendar.
   * 
   * @param calendar The calendar for which to retrieve the events
   * @return The List of events for the calendar
   * @throws IOException
   */
  public EventList getEvents(Calendar calendar) throws IOException {
    return executeGet(new CalendarUrl(calendar.getEventsFeedLink()), null, false, EventList.class);
  }

  /**
   * Returns the Access Control List for the calendar.
   * 
   * @param calendar The calendar for which to retrieve the access control list
   * @return The access control list for the calendar
   * @throws IOException
   */
  public AccessControlList getAccessControlList(Calendar calendar) throws IOException {
    return executeGet(new CalendarUrl(calendar.getAccessControlListLink()), null, false,
        AccessControlList.class);
  }

  /**
   * Returns the FreeBusy feed for the user.
   * 
   * @param userId The user for which to retrieve the FreeBusy feed.
   * @return The FreeBusy feed for the user.
   * @throws IOException
   */
  public FreeBusy getUserFreeBusy(String userId) throws IOException {
    return executeGet(UrlFactory.getUserFreeBusyFeedUrl(userId), null, false, FreeBusy.class);
  }

  // ============= HTTP Requests Methods ================

  /**
   * Override executeGet method to handle redirection. Gets the given
   * {@code url} and matches the results to the given {@code feedClass} Type.
   * Returns {@code null} if the etag matches.
   * 
   * @param url The URL of the Feed or Entry to get.
   * @param etag etag of the current version of the entry to get, this does a
   *          conditional get, {@code null} will be the response if the given
   *          etag is the one of the current server. If {@code null} this will
   *          be a normal/non-conditional Get.
   * @param doPartialGet true if we should only request the fields that are
   *          described in the {@code feedClass}. This uses the {@code fields}
   *          GData URL parameter.
   * @param feedClass The Class to which the responses values must me matched,
   *          this will also be the type of the response.
   * @return An instance of the {@code feedClass} Class with the response values
   *         mapped to its fields.
   * @throws IOException
   */
  @Override
  public <FeedOrEntryType> FeedOrEntryType executeGet(GoogleUrl url, String etag,
      boolean doPartialGet, Class<? extends FeedOrEntryType> feedClass) throws IOException {
    return executeGet(url, etag, doPartialGet, feedClass, 0);
  }

  /**
   * Override executeInsert method to handle redirection. Inserts the given
   * {@code entry} to the given {@code feedPostUrl}.
   * 
   * @param entry
   * @param feedPostUrl
   * @return
   * @throws IOException
   */
  @Override
  public <FeedOrEntryType> FeedOrEntryType executeInsert(FeedOrEntryType entry,
      GoogleUrl feedPostUrl) throws IOException {
    return executeInsert(entry, feedPostUrl, 0);
  };

  /**
   * Override the executeBatch method to handle redirection. Executes the given
   * batch request.
   * 
   * @param batchfeed The Batch feed containing all the operations to execute
   * @param feedBatchUrl The URL where to post the batch feed
   * @return The batch feed response
   * @throws IOException
   */
  @Override
  public <BatchFeedType extends BatchFeed> BatchFeedType executeBatch(BatchFeedType batchfeed,
      GoogleUrl feedBatchUrl) throws IOException {
    return executeBatch(batchfeed, feedBatchUrl, 0);
  };

  /**
   * Gets the given {@code url} and matches the results to the given
   * {@code feedClass} Type. Returns {@code null} if the etag matches.
   * 
   * @param url The URL of the Feed or Entry to get.
   * @param etag etag of the current version of the entry to get, this does a
   *          conditional get, {@code null} will be the response if the given
   *          etag is the one of the current server. If {@code null} this will
   *          be a normal/non-conditional Get.
   * @param doPartialGet true if we should only request the fields that are
   *          described in the {@code feedClass}. This uses the {@code fields}
   *          GData URL parameter.
   * @param feedClass The Class to which the responses values must me matched,
   *          this will also be the type of the response.
   * @param attempt The current number of attempt, when it reaches
   *          maxRedirectAttempt, stop following redirect
   * @return An instance of the {@code feedClass} Class with the response values
   *         mapped to its fields.
   * @throws IOException
   */
  protected <FeedOrEntryType> FeedOrEntryType executeGet(GoogleUrl url, String etag,
      boolean doPartialGet, Class<? extends FeedOrEntryType> feedClass, int attempt)
      throws IOException {
    try {
      return super.executeGet(url, etag, doPartialGet, feedClass);
    } catch (HttpResponseException e) {
      // Check for redirect.
      if (e.response.statusCode == 302 && attempt < maxRedirectAttempt
          && e.response.headers.location != null) {
        GoogleUrl location = new GoogleUrl(e.response.headers.location);

        gsessionid = (String) location.getFirst("gsessionid");
        e.response.ignore(); // force the connection to close.
        return executeGet(url, etag, doPartialGet, feedClass, attempt + 1);
      }

      throw e;
    }
  }

  /**
   * Inserts the given {@code entry} to the given {@code feedPostUrl}.
   * 
   * @param entry
   * @param feedPostUrl
   * @param attempt The current number of attempt, when it reaches
   *          maxRedirectAttempt, stop following redirect
   * @return
   * @throws IOException
   */
  protected <FeedOrEntryType> FeedOrEntryType executeInsert(FeedOrEntryType entry,
      GoogleUrl feedPostUrl, int attempt) throws IOException {
    try {
      return super.executeInsert(entry, feedPostUrl);
    } catch (HttpResponseException e) {
      // Check for redirect.
      if (e.response.statusCode == 302 && attempt < maxRedirectAttempt
          && e.response.headers.location != null) {
        GoogleUrl location = new GoogleUrl(e.response.headers.location);

        gsessionid = (String) location.getFirst("gsessionid");
        e.response.ignore(); // force the connection to close.
        return executeInsert(entry, feedPostUrl, attempt + 1);
      }

      throw e;
    }
  };

  /**
   * Executes the given batch request.
   * 
   * @param batchfeed The Batch feed containing all the operations to execute
   * @param feedBatchUrl The URL where to post the batch feed
   * @param attempt The current number of attempt, when it reaches
   *          maxRedirectAttempt, stop following redirect.
   * @return The batch feed response
   * @throws IOException
   */
  protected <BatchFeedType extends BatchFeed> BatchFeedType executeBatch(BatchFeedType batchfeed,
      GoogleUrl feedBatchUrl, int attempt) throws IOException {
    try {
      return super.executeBatch(batchfeed, feedBatchUrl);
    } catch (HttpResponseException e) {
      // Check for redirect.
      if (e.response.statusCode == 302 && attempt < maxRedirectAttempt
          && e.response.headers.location != null) {
        GoogleUrl location = new GoogleUrl(e.response.headers.location);

        gsessionid = (String) location.getFirst("gsessionid");
        e.response.ignore(); // force the connection to close.
        return executeBatch(batchfeed, feedBatchUrl, attempt + 1);
      }

      throw e;
    }
  };

  // ========================== Main for tests ========================

  /**
   * Main for testing
   * 
   * @param args
   * @throws IOException
   */
  public static void main(String[] args) throws IOException {
    LoggingUtils.enableLogging();
    CalendarService cs = new CalendarService("myTestApp");
    cs.setClientLoginCredentials("appsdevrel.test@gmail.com", "appsdevrel.test#android");

    SettingsList settings = cs.getSettings();

    for (SettingsEntry s : settings.settings) {
      System.out.println(s.property.name + ": " + s.property.value);
    }

    System.out.println("Find timezone: " + settings.getProperty("timezone"));

    CalendarList ss = cs.getAllCalendars();
    for (Calendar c : ss.calendars) {
      EventList events = cs.getEvents(c);

      if (events.events != null)
        for (Event e : events.events) {
          List<Reminder> reminders = e.getReminders();

          System.out.println("  > " + e.title + " - " + e.eventStatus.value);
          if (reminders != null)
            for (Reminder r : reminders) {
              System.out.println("    -> " + r.method + " - " + r.minutes);
            }
        }
    }

  }
}
